"""
    2021-8-13   0:40    by  CYL@home
    后续任务：
    删除单元格之类的。
    编写合并行之类的。
"""
import pandas
import xlrd
import openpyxl
import re
def chg_excel_to_dataframe(excel_file_Path:str,sheetName:str="Sheet1",LineIndex:int=0,ColumnIndex:int=0,mode="指定行索引")->pandas.core.frame.DataFrame:
    """
        2021-08-12  23:41   by  CYL
        函数名：
            chg_excel_to_dataframe()
        函数说明：
            将.xls或.xlsx文件中的数据转换成pandas的DataFrame格式，
            并返回该格式的对象。
        关联库：
            re库。
            除了pandas库的支持外。
            .xls需要xlrd的支持。
            .xlsx需要openpyxl的支持。
        推荐的使用方法：
            一般使用"指定列索引"(列索引为某一行，行索引从0开始)或"不指定索引"(即默认列索引为第一行，行索引从0开始)。
            原因，使用add_new_line_into_DataFrame()添加某一行时，行索引由于都是系统自带的从0开始，这样会方便些。
        缺陷：
            虽然添加了对传入文件的后缀名的分析，但还没有达到限制传入的能力。
    :param excel_file_Path: 即被转换用的.xls或.xlsx文件的路径。
    :param sheetName: 指定读取excel中的哪个Sheet至DataFrame。默认为：Sheet1.
    :param LineIndex: 指定DataFrame行索引为哪一列。默认为0，即用excel中的第一列作为行索引。
    :param ColumnIndex: 指定DataFrame的列索引为excel中的哪一行。默认为0，即用excel中的第一行作为列索引。
    :param mode:    用于指定DataFrame中的行列索引是否需要和excel表中互相对应的模式。
    :return:    一个pandas库的DataFrame格式的对象。
    """
    xls_pttn = "(.xls)$"
    xlsx_pttn = "(.xlsx)$"
    suffix_result = re.findall(xls_pttn,excel_file_Path)    #   用于检测传入文件的后缀名。
    if len(suffix_result) == 0: #   当传入文件的后缀名不为.xls结尾时。
        suffix_result = re.findall(xlsx_pttn, excel_file_Path)
    else:
        pass
    print("suffix_result = ", suffix_result,"aim:用于显示传入文件的后缀名。")
    if mode == "指定行列索引":
        if suffix_result == [".xls"]:
            DF = pandas.read_excel(excel_file_Path,sheet_name=sheetName,index_col=LineIndex,header=ColumnIndex)
            print("DF = ",DF,type(DF),"aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF
        elif suffix_result == [".xlsx"]:
            DF_X = pandas.read_excel(excel_file_Path,sheet_name=sheetName,index_col=LineIndex,header=ColumnIndex)
            print("DF_X = ",DF_X,type(DF_X),"aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF_X
    elif mode == "指定行索引":
        if suffix_result == [".xls"]:
            DF = pandas.read_excel(excel_file_Path, sheet_name=sheetName, index_col=LineIndex)
            print("DF = ", DF, type(DF), "aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF
        elif suffix_result == [".xlsx"]:
            DF_X = pandas.read_excel(excel_file_Path, sheet_name=sheetName, index_col=LineIndex)
            print("DF_X = ", DF_X, type(DF_X), "aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF_X
    elif mode == "指定列索引":
        if suffix_result == [".xls"]:
            DF = pandas.read_excel(excel_file_Path, sheet_name=sheetName, header=ColumnIndex)
            print("DF = ", DF, type(DF), "aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF
        elif suffix_result == [".xlsx"]:
            DF_X = pandas.read_excel(excel_file_Path, sheet_name=sheetName, header=ColumnIndex)
            print("DF_X = ", DF_X, type(DF_X), "aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF_X
    elif mode == "不指定索引":
        if suffix_result == [".xls"]:
            DF = pandas.read_excel(excel_file_Path, sheet_name=sheetName)
            print("DF = ", DF, type(DF), "aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF
        elif suffix_result == [".xlsx"]:
            DF_X = pandas.read_excel(excel_file_Path, sheet_name=sheetName)
            print("DF_X = ", DF_X, type(DF_X), "aim:用于显示chg_excel_to_dataframe()的结果。")
            return DF_X
    else:
        print("chg_excel_to_dataframe()函数啥也没做！建议检查mode设置！")

def chg_dataframe_to_excel(DataFrame:pandas.core.frame.DataFrame,DF_columns:list=None,sheetName:str="Sheet1",newExcelName:str=r"./DataFrameToExcel_byPython",newExcelSuffix:str=".xlsx",encodeType="utf-8",na:str="NULL",inf:int=0):
    """
    2021-08-13  0:21    by  CYL
    函数名：
        chg_dataframe_to_excel()
    函数说明：
        用于将pandas的DataFrame给转换为.xls或.xlsx。
        默认将全部的DataFrame列内容，以./DataFrameToExcel_byPython.xlsx作为文件输出，字符编码格式默认utf-8。
    注意：
        转化为.xls的操作可能在后续的pandas库版本中不再支持。
        当前pandas库版本为 pandas 1.3.1
        推荐以后均以.xlsx作为pandas的输出。
    关联库：
        pandas库 openpyxl库 xlwt库(将来可能不支持)
    :param DataFrame:   需要转化为.xls或.xlsx的DataFrame源。
    :param DF_columns:  设置需要导出的列，默认导出全部的列。
    :param sheetName:   设置DataFrame导出至.xls或.xlsx中的哪个Sheet中。
    :param newExcelName:    设置DataFrame导出至.xls或.xlsx文件的路径及文件名，默认"./DataFrameToExcel_byPython"，不带文件后缀。（会自动新建.xls或.xlsx文件）
    :param newExcelSuffix:  设置.xls或.xlsx文件后缀，默认以.xlsx作为后缀。
    :param encodeType:      设置.xls或.xlsx文件的字符编码格式，默认utf-8。
    :param na:  设置缺省值，默认为字符串“NULL”。
    :param inf: 设置无穷值，默认为数字0。
    :return:    无。
    """
    new_excel_path = newExcelName + newExcelSuffix
    if DF_columns==None:
        DataFrame.to_excel(excel_writer=new_excel_path,
                           sheet_name=sheetName,
                           index=False,
                           encoding = encodeType,
                           na_rep = na,         #缺失值填充为"NULL"，可设置为0或其他的。
                           inf_rep = inf        #无穷值填充为0。
                           )
        print(f"{new_excel_path}已导出成功！")
    else:
        DataFrame.to_excel(excel_writer=new_excel_path,
                           sheet_name=sheetName,
                           index=False,
                           columns = DF_columns,#导出DataFrame中的某些列，例如["用户ID","7月销量","8月销量"]这些DataFrame列。
                           encoding = encodeType,
                           na_rep = na,         #缺失值填充为"NULL"，可设置为0或其他的。
                           inf_rep = inf        #无穷值填充为0。
                           )
        print(f"{new_excel_path}已导出成功！")

def chg_dataframe_to_txt(DataFrameGen:pandas.core.frame.DataFrame,LineHead:str="Line",LineContentSeparator:str=" ",LineEnd:str='\n',OUTPUT_TXT_PATH:str=r"./DataFrameToTxT_byPython.txt"):
    """
        2021-08-13  10:01   by  CYL
        函数名：
            chg_dataframe_to_txt()
        函数说明：
            将pandas的DataFrame数据表全部转换成.txt格式。
    :param DataFrameGen:    需要被转换为.txt格式的DataFrame源。
    :param LineHead:        .txt中每一行的行号。默认以Line（数字）+" "开头。
    :param LineContentSeparator:    .txt中每一行中每一列之间的分隔符。默认以" "作为分隔符。
    :param LineEnd:         .txt中每一行的结尾符。默认以"\n"作为结尾。
    :param OUTPUT_TXT_PATH: .txt的输出路径。
    :return:    无。
    """
    OUTTXT_File_HND = open(OUTPUT_TXT_PATH, "w")
    for line_index in range(0, len(DataFrameGen.index)):
        OUTTXT_File_HND.write(LineHead+f"{line_index} ")
        for column_index in range(0, len(DataFrameGen.columns)):
            OUTTXT_File_HND.write(str(DataFrameGen.iloc[line_index][column_index]))
            OUTTXT_File_HND.write(LineContentSeparator)
        OUTTXT_File_HND.write(LineEnd)
    OUTTXT_File_HND.close()
    print(f"{OUTPUT_TXT_PATH}已生成完毕。")

def dataframe_lines_select(lines:list,DataFrameGen:pandas.core.frame.DataFrame,mode:str="单行iloc")->pandas.core.frame.DataFrame:
    """
        2021-08-13  11:31   by  CYL
        函数名：
            dataframe_lines_select()
        函数说明：
            用于DataFrame中的行选择，并产生新的DataFrame作为输出。
    :param lines:   指定的行。
    :param DataFrameGen:    DataFrame数据源。
    :param mode:    指定行的方式。
    :return:    一个新的DataFrame。
    """
    if mode == "单行iloc":
        #   例如：df.iloc[0]即意味着选择excel中的第1行进行输出，但此时为输出pandas.core.series.Series格式，不为DataFrame。
        lines_selected_dataframe = DataFrameGen.iloc[lines[0]]
    elif mode == "多行分离式iloc":
        #   例如：df.iloc[[0,1]]即意味着选择excel中的第1行和第2行作为新的DataFrame输出。
        lines_selected_dataframe = DataFrameGen.iloc[lines]
    elif mode == "多行连续式iloc":
        #   例如：df.iloc[0:3]即意味着连续选择第1行到第4行之间的值作为新的DataFrame输出，不包括第4行。
        lines_selected_dataframe = DataFrameGen.iloc[lines[0]:lines[1]]
    else:
        pass
    print("lines_selected_dataframe = ",lines_selected_dataframe,type(lines_selected_dataframe),"aim:用于显示选择过后的行。")
    return lines_selected_dataframe

def dataframe_columns_select(columns:list,DataFrameGen:pandas.core.frame.DataFrame,mode:str="列索引名")->pandas.core.frame.DataFrame:
    """
        2021-08-13  10:49   by  CYL
        函数名：
            dataframe_columns_select()
        函数说明：
            将原始的DataFrame中的几列选出来，作为新的DataFrame输出。
    :param columns:    一个列表，即需要挑选出来的那几个列的行索引。
    :param DataFrameGen:    原始的DataFrame源。
    :param mode:    分为三种，见下方#中的内容。
    :return:    一个新的DataFrame。
    """
    if mode=="列索引名":
        #   例如：df[["订单编号","客户姓名"]]  也是分离式获取，获取名为"订单编号"的列数据和名为"客户姓名"的列数据。
        columns_selected_dataframe = DataFrameGen[columns]
    elif mode =="分离式iloc":
        #   例如：df.iloc[:,[0,2]] 意味着获取第1列和第三列的数值。
        columns_selected_dataframe = DataFrameGen.iloc[:,columns]
    elif mode =="连续式iloc":
        #   例如:df.iloc[:,0:3] 意味着连续获取第1列到第4列之间的值，不包括第4列。
        columns_selected_dataframe = DataFrameGen.iloc[:, columns[0]:columns[1]]
    else:
        pass
    print("columns_selected_dataframe = ",columns_selected_dataframe,"aim:用于显示列选择后的DataFrame。")
    return columns_selected_dataframe

def deepcopy_a_DataFrame(DataFrameGen:pandas.core.frame.DataFrame)->pandas.core.frame.DataFrame:
    """
        2021-08-13  14:29   by  CYL
        函数名：
            deepcopy_a_DataFrame()
        函数说明:
            用于镜像一个DataFrame()并返回。
    :param DataFrameGen:    被镜像的DataFrame。
    :return:    新的DataFrame。
    """
    newDataFrame = DataFrameGen.copy(deep=True)
    return newDataFrame

def add_new_column_into_DataFrame(DataFrameGen:pandas.core.frame.DataFrame,insertAfterColumn:int,newColumnIndexName:str,newColumnContent:list,mode = "insert in newDataFrame")->pandas.core.frame.DataFrame:
    """
        2021-08-13  12:51   by  CYL
        函数名：
            add_new_column_into_DataFrame()
        函数说明：
            用于向原来的DataFrame中插入一列数据。
    :param DataFrameGen:    要被插入的原DataFrame。
    :param insertAfterColumn:   在第insertAfterColumn列后插入列。
    :param newColumnIndexName:  新插入列的列索引名称。
    :param newColumnContent:    新插入列的数据，注意，该列表长度一定要与原DataFrame一致。
    :param mode:    选择插入后的输出方式，默认为不改变原DataFrame，输出一个新的DataFrame。
    :return:    若mode="insert in GenDataFrame"，则在原DataFrame上做修改。
                若mode="insert in newDataFrame"，不改变原DataFrame，并返回一个新的DataFrame。
    """
    if mode == "insert in GenDataFrame":    #   改变原DataFrame，即在原DataFrame中插入值。
        DataFrameGen.insert(insertAfterColumn,newColumnIndexName,newColumnContent)
        print("DataFrameGen = ",DataFrameGen,"aim:用于测试新增列后的原DataFrame。")
        return DataFrameGen
    elif mode == "insert in newDataFrame":  #   不改变原DataFrame，深层拷贝一个DataFrame,并改变该DataFrame后，作为输出。
        newDataFrame = DataFrameGen.copy(deep=True)
        newDataFrame.insert(insertAfterColumn,newColumnIndexName,newColumnContent)
        print("newDataFrame = ", newDataFrame, "aim:用于测试新增列后的新DataFrame。")
        return newDataFrame

def get_DataFrame_shape(DataFrameGen:pandas.core.frame.DataFrame)->(int,int,list):
    """
        2021-08-13  12:56   by  CYL
        函数名：
            get_DataFrame_shape()
        函数说明：
            用于获取传入的DataFrame的最大行数和列数。
        注意：
            在add_new_line_into_DataFrame()中被使用，故.py文件中需要此函数放在add_new_line_into_DataFrame()上。
    :param DataFrameGen:    需要被分析的DataFrame。
    :return:    返回一个列表，即(最大行数，最大列数,列索引的列表)。
    """
    the_GenDF_lineMax = DataFrameGen.shape[0]   # 获取原DataFrame的最大行数。
    the_GenDF_columnMax = DataFrameGen.shape[1] # 获取原DataFrame的最大列数。
    the_GenDF_columnIndexName = DataFrameGen.columns
    print(f"目前{DataFrameGen}\n共有{the_GenDF_lineMax}行{the_GenDF_columnMax}列！")
    print(f"列索引为{the_GenDF_columnIndexName}")
    return (the_GenDF_lineMax,the_GenDF_columnMax,the_GenDF_columnIndexName)

def add_new_line_into_DataFrame(DataFrameGen:pandas.core.frame.DataFrame,insertAfterLine:int,newLineContent:list)->pandas.core.frame.DataFrame:
    """
        2021-08-13  13:58   by  CYL
        函数名：
            add_new_line_into_DataFrame()
        函数说明：
            向DataFrame中添加一行数据，并返回新的DataFrame。
    :param DataFrameGen:    被插入行的DataFrame。
    :param insertAfterLine: 在哪行后插入行。
    :param newLineContent:  需要插入的内容，注意最好是与列数长度一致的列表。
    :return:    一个新的DataFrame。
    """
    the_GenDF_lineMax = DataFrameGen.shape[0]   # 获取原DataFrame的最大行数。
    the_GenDF_columnMax = DataFrameGen.shape[1] # 获取原DataFrame的最大列数。
    if insertAfterLine<=the_GenDF_lineMax:
        the_GenDF_part1 = dataframe_lines_select([0,insertAfterLine],DataFrameGen,"多行连续式iloc")
        the_GenDF_part2 = dataframe_lines_select([insertAfterLine,the_GenDF_lineMax],DataFrameGen,"多行连续式iloc")
        the_wanna_insertDF = pandas.DataFrame(newLineContent).T #   pandas.DataFrame()默认生成新列，使用T转换为新行。
        the_wanna_insertDF.columns = DataFrameGen.columns       #   将新增行的列索引与原DataFrame一致。
        the_new_DF = pandas.concat([the_GenDF_part1,the_wanna_insertDF,the_GenDF_part2],ignore_index = True)
        print("the_new_DF = ",the_new_DF,"aim:用于显示新增行后新的DataFrame。")
        return the_new_DF
    else:
        get_DataFrame_shape(DataFrameGen)
        print("add_new_line_into_DataFrame()啥也没干！请检查输入的行数！")

def chg_DataFrame_cell_value(DataFrameGen:pandas.core.frame.DataFrame,Line:int,Column:int,cellContent:str,mode = "change in GenDataFrame")->pandas.core.frame.DataFrame:
    """
        2021-8-13   14:36   by  CYL
        函数名：
            chg_DataFrame_cell_value()
        函数说明：
            用于改变DataFrame中的某个cell中的值。
    :param DataFrameGen:    cell所在的DataFrame。
    :param Line:    cell所在的行。
    :param Column:  cell所在的列。
    :param cellContent: cell所需要被修改的内容。
    :param mode:    cell内容被修改后，以什么形式输出DataFrame。默认是在原DataFrame上修改。
    :return:    当mode = "change in newDataFrame"后，不改变原始的DataFrame,在新的DataFrame中修改，并返回新的DataFrame。
    """
    if mode== "change in GenDataFrame":
        DataFrameGen.iloc[Line,Column]=cellContent
        print("修改cell后的DataFrameGen = ",DataFrameGen,"aim:用于查看修改cell后的原DataFrameGen")
        return DataFrameGen
    elif mode == "change in newDataFrame":
        newDataFrame = deepcopy_a_DataFrame(DataFrameGen)
        newDataFrame.iloc[Line,Column]=cellContent
        print("修改cell后的newDataFrame = ",newDataFrame,"aim:用于查看修改cell后的新newDataFrame")
        return newDataFrame

def delete_Columns_in_DataFrame(DataFrameGen:pandas.core.frame.DataFrame,deleteColumnIndexName:list,mode = "delete in newDataFrame")->pandas.core.frame.DataFrame:
    """
        2021-08-13  16:52   by  CYL
        函数名：
            delete_columns_in_DataFrame()
        函数说明：
            删除DataFrame中的某一列，默认不在原DataFrame中进行，即默认最终会生成一个新的DataFrame进行返回。
    :param DataFrameGen:    需要被删除列的DataFrame。
    :param deleteColumnIndexName:   需要被删除的列的列索引。
    :return:    默认返回一个新的DataFrame。若mode = "delete in GenDataFrame"则会删除原DataFrame中的内容。
    """
    # 例如：df.drop(columns = ["销售ID","成交时间"])
    # 例如：df.drop(["销售ID","成交时间"],axis=1)
    # 例如:df.drop(df.columns[[4,5]],axis=1)
    if mode == "delete in newDataFrame":    # 不在原DataFrame中进行删除。
        newDataFrame = DataFrameGen.drop(deleteColumnIndexName,axis = 1,inplace=False)      #   inplace=False时，意味着修改对原表无效。
                                                                                            #   axis=0，修改行;axis=1，修改列。
        print("newDataFrame = ",newDataFrame,"aim:用于显示删除列后的newDataFrame。")
        return newDataFrame
    elif mode == "delete in GenDataFrame":  # 在原DataFrame中进行删除。
        DataFrameGen.drop(deleteColumnIndexName, axis=1, inplace=True)
        print("DataFrameGen = ", DataFrameGen, "aim:用于显示删除列后的原DataFrameGen。")
        return DataFrameGen

def delete_Lines_in_DataFrame(DataFrameGen:pandas.core.frame.DataFrame,deleteLineIndexName:list,mode = "delete in newDataFrame")->pandas.core.frame.DataFrame:
    """
        2021-8-13   17:28   by  CYL
        函数名：
            delete_Lines_in_DataFrame()
        函数说明：
            用于删除DataFrame中的某一行，默认不在原DataFrame上进行操作，即默认最终会返回一个新的DataFrame。
    :param DataFrameGen:    需要被删除行的DataFrame。
    :param deleteLineIndexName: 需要被删除的行的行索引。
    :param mode:    默认返回一个新的DataFrame。若mode = "delete in GenDataFrame"则会删除原DataFrame中的内容。
    :return:
    """
    # 例如：df.drop(index = ["0a","9b"])删除行索引为0a，9b的行。
    # 例如：df.drop(["0a","9b"],axis = 0)
    # 例如：df.drop(df.index[[0,3]],axis=0)删除第一行和第四行的数据。
    if mode == "delete in newDataFrame":    # 不在原DataFrame中进行删除。
        newDataFrame = DataFrameGen.drop(deleteLineIndexName,axis = 0,inplace=False)        #   inplace=False时，意味着修改对原表无效。
                                                                                            #   axis=0，修改行;axis=1，修改列。
        print("newDataFrame = ",newDataFrame,"aim:用于显示删除行后的newDataFrame。")
        return newDataFrame
    elif mode == "delete in GenDataFrame":  # 在原DataFrame中进行删除。
        DataFrameGen.drop(deleteLineIndexName, axis=0, inplace=True)
        print("DataFrameGen = ", DataFrameGen, "aim:用于显示删除行后的原DataFrameGen。")
        return DataFrameGen


def pandas_demo():
    xls_file_path = r"./pandas_test_0.xls"
    xlsx_file_path = r"./pandas_test_1.xlsx"
    # 导入一个.xls文件至pandas
    DF_XLS = chg_excel_to_dataframe(xls_file_path,mode="不指定索引")
    # 导入一个.xlsx文件至pandas
    DF_XLSX = chg_excel_to_dataframe(xlsx_file_path,mode="不指定索引")
    # 将pandas的DataFrame导出为.xlsx
    chg_dataframe_to_excel(DF_XLS)
    # 将pandas的DataFrame导出为.xls
    chg_dataframe_to_excel(DF_XLSX,newExcelSuffix=".xls")
    # 将pandas的DataFrame导出为.txt
    chg_dataframe_to_txt(DF_XLS)
    # 选择连续的几列DataFrame作为新的DataFrame
    dataframe_columns_select([0,2],DF_XLS,"连续式iloc")
    # 选择单独的一行DataFrame作为新的DataFrame
    dataframe_lines_select([1],DF_XLS)
    # 在原DataFrame上新增一列数据
    add_new_column_into_DataFrame(DF_XLS,2,"newColumn",["这是新增的","看看","pia","rua","hanhan"])
    # 在原DataFrame上新增一行数据
    add_new_line_into_DataFrame(DF_XLS,5,[1,2,3,"乌拉","han"])
    # 修改DataFrame上的一个cell里的值
    chg_DataFrame_cell_value(DF_XLS,0,0,"乌拉！")
    # 删除DataFrame上的某一列，不在原DataFrame中进行，而是新生成一个DataFrame后，再在新的上面进行删除并返回。
    delete_Columns_in_DataFrame(DF_XLS,["第一列"])
    # 删除DataFrame上的某一列，在原DataFrame中进行。
    delete_Columns_in_DataFrame(DF_XLS,["第一列"],mode="delete in GenDataFrame")
    # 删除DataFrame上的某一行，不在原DataFrame中进行，而是新生成一个DataFrame后，再在新的上面进行删除并返回。
    delete_Lines_in_DataFrame(DF_XLS,[0])
    # 删除DataFrame上的某一行，在原DataFrame中进行。
    delete_Lines_in_DataFrame(DF_XLS, [2], mode="delete in GenDataFrame")

if __name__=="__main__":
    pandas_demo()